// $Id: aggregate.hpp 5 2009-09-05 10:21:39Z joseph2002 $
/*
 * Copyright 2009 Joseph Wu
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
 *
 * For more information, see http://xpcomplus.sourceforge.net
 */

#ifndef XPCOM_AGGREGATE_HPP
#define XPCOM_AGGREGATE_HPP

#include <xpcom/config.hpp>
#include <boost/mpl/begin_end.hpp>
#include <boost/mpl/empty.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/aux_/na.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/enum_params_with_a_default.hpp>
#include <boost/type_traits/is_same.hpp>
#include <xpcom/assert.hpp>
#include <xpcom/component.hpp>
#include <xpcom/interface.hpp>
#include <xpcom/object.hpp>
#include <xpcom/ptr.hpp>

#ifdef BOOST_HAS_ABI_HEADERS
#include BOOST_ABI_PREFIX
#endif

#ifndef XPCOM_LIMIT_AGGREGATE_INTERFACE_SIZE
#  define XPCOM_LIMIT_AGGREGATE_INTERFACE_SIZE BOOST_MPL_LIMIT_VECTOR_SIZE
#endif

#if XPCOM_LIMIT_AGGREGATE_INTERFACE_SIZE < BOOST_MPL_LIMIT_VECTOR_SIZE
#  define XPCOM_AGGREGATE_INTERFACE_SIZE XPCOM_LIMIT_AGGREGATE_INTERFACE_SIZE
#else
#  define XPCOM_AGGREGATE_INTERFACE_SIZE BOOST_MPL_LIMIT_VECTOR_SIZE
#endif

class nsIComponentManager;

namespace xpcom {

//
// Factory model tags.
//
namespace factory_model {

struct aggregatable_factory_tag {};

} // namespace xpcom::factory_model

namespace detail {

// Test whether T can provide interface of iid.
template <typename T>
inline bool is_interface_compatible(const uuid& iid)
{
    if (iid == typeid_of<T>())
        return true;
    else
        return is_interface_compatible<typename type_info<T>::base_type>(iid);
};

template <>
inline bool is_interface_compatible<interface>(const uuid&)
{
    return false;
}

template <>
inline bool is_interface_compatible<boost::mpl::void_>(const uuid&)
{
    return false;
}

// Find if there is compatible interface for given iid in the sequence.
template <bool done = true>
struct find_interface_impl
{
    template <typename Iterator, typename LastIterator>
    static bool execute(Iterator*, LastIterator*, const uuid&)
    {
        return false;
    }
};

template <>
struct find_interface_impl<false>
{
    template <typename Iterator, typename LastIterator>
    static bool execute(Iterator*, LastIterator*, const uuid& iid)
    {
        typedef typename boost::mpl::deref<Iterator>::type item;

        if (is_interface_compatible<item>(iid))
            return true;

        typedef typename boost::mpl::next<Iterator>::type iter;
        return find_interface_impl<
                boost::is_same<iter, LastIterator>::value
            >::execute((iter*)0, (LastIterator*)0, iid);
    }
};

template <typename Seq>
inline bool find_interface(const uuid& iid)
{
    typedef typename boost::mpl::begin<Seq>::type first;
    typedef typename boost::mpl::end<Seq>::type last;

    return find_interface_impl<
            boost::is_same<first, last>::value
        >::execute((first*)0, (last*)0, iid);
}

} // namespace xpcom::detail

//
// Custom interface implementation that aggregates other components.
// If the interface sequence is empty, aggregate<> will delegate all query
// to the inner object.
//
template <
    BOOST_PP_ENUM_PARAMS_WITH_A_DEFAULT(
        XPCOM_AGGREGATE_INTERFACE_SIZE, typename T, boost::mpl::na)>
class aggregate : public custom_interface_tag
{
    typedef typename boost::mpl::vector<
        BOOST_PP_ENUM_PARAMS(XPCOM_AGGREGATE_INTERFACE_SIZE, T)
    >::type interfaces_type;

public:
    result_t query(void*, const uuid& iid, void** obj)
    {
        // Null inner object is accepted
        //XPCOM_ASSERT(inner_);
        if (!inner_)
            return NS_ERROR_NO_INTERFACE;

        // It should not possible iid = typeid_of<interface> here
        XPCOM_ASSERT(iid != typeid_of<interface>());

        // If interfaces_type is empty, we are delegating all queries
        if (!boost::mpl::empty<interfaces_type>::value) {
            if (!detail::find_interface<interfaces_type>(iid))
                return NS_ERROR_NO_INTERFACE;
        }

        return inner_.get()->query_interface(iid, obj);
    }

protected:
    void inner(noref_ptr<interface> inner)
    {
        inner_ = inner;
    }

    template <typename Class>
    void inner(
            Class* pthis,
            const uuid& clsid,
            noref_ptr<nsIComponentManager> manager = 0)
    {
        XPCOM_ASSERT(pthis);
        inner_ = components(clsid, upcast_to_interface(pthis), manager);
    }

    template <typename Class>
    void inner(
            Class* pthis,
            const char* contractid,
            noref_ptr<nsIComponentManager> manager = 0)
    {
        XPCOM_ASSERT(pthis);
        inner_ = components(contractid, upcast_to_interface(pthis), manager);
    }

private:
    ref_ptr<interface> inner_;
};

// Auxiliary interface for implementing aggregation.
class XPCOM_NO_VTABLE inner_interface
{
public:
    XPCOM_IMETHOD inner_query_interface(const uuid& iid, void** obj) = 0;
    XPCOM_IMETHOD_(refcnt_t) inner_add_ref() = 0;
    XPCOM_IMETHOD_(refcnt_t) inner_release() = 0;
};

class XPCOM_NO_VTABLE aggregatable_interface : public inner_interface
{
public:
    XPCOM_IMETHOD_(void) outer(noref_ptr<interface> outer) = 0;
};

//
// XPCOM component implementation adapter which supports aggregation.
// Template parameter Base can be any type that implements interface.
//
// aggregatable<> can be made to final component of the module, and this is the
// preferred use case.
//
template <typename Base>
class aggregatable :
    public Base, public aggregatable_interface
{
    typedef Base base_type;
public:
    typedef typename base_type::thread_model_tag thread_model_tag;
    typedef factory_model::aggregatable_factory_tag factory_model_tag;

    aggregatable()
    {
        outer_ = upcast_to_interface().get();
    }

    noref_ptr<interface> upcast_to_interface()
    {
        return reinterpret_cast<interface*>(
                static_cast<inner_interface*>(this));
    }

    // interface
    XPCOM_IMETHODIMP query_interface(const uuid& iid, void** obj)
    {
        XPCOM_ASSERT(outer_);
        return outer_->query_interface(iid, obj);
    }

    XPCOM_IMETHODIMP_(refcnt_t) add_ref()
    {
        XPCOM_ASSERT(outer_);
        return outer_->add_ref();
    }

    XPCOM_IMETHODIMP_(refcnt_t) release()
    {
        XPCOM_ASSERT(outer_);
        return outer_->release();
    }

    // inner_interface
    XPCOM_IMETHODIMP inner_query_interface(const uuid& iid, void** obj)
    {
        // query_interface to root interface always returns inner_interface
        if (iid == typeid_of<interface>()) {
            *obj = static_cast<inner_interface*>(this);
            inner_add_ref();
            return NS_OK;
        }

        return base_type::query_interface(iid, obj);
    }

    XPCOM_IMETHODIMP_(refcnt_t) inner_add_ref()
    {
        return base_type::add_ref();
    }

    XPCOM_IMETHODIMP_(refcnt_t) inner_release()
    {
        return base_type::release();
    }

    // aggregatable_interface
    XPCOM_IMETHODIMP_(void) outer(noref_ptr<interface> outer)
    {
        outer_ = outer.get();
    }

private:
    interface* outer_;
};

} // namespace xpcom

#undef XPCOM_AGGREGATE_INTERFACE_SIZE

#ifdef BOOST_HAS_ABI_HEADERS
#include BOOST_ABI_SUFFIX
#endif

#endif // XPCOM_AGGREGATE_HPP
